Part 1 Part 2 Part 3 Part 4 Part 5 Postscript
I thought I’d have a go at writing some code that could print things. A pretty-printer, if you like. What I want to be able to do is this:
// Print x correctly, where x is ANY type.
cout << prettyprint(x) << endl;
Obviously this is going to involve templates since the type of x
may vary.
There are a few things that immediately spring to mind:
- Maybe
x
is already printable, that is,operator<<
might work "out of the box". - If
x
is a container, we want to do something with that and print the elements. - We probably want to print elements of
pair
andtuple
also. - The value of some things is not printable, or at least not sensible to print. For example, functions.
The basic plan that came to mind is this: prettyprint
is a function template, which returns an object that wraps the x
and has operator<<
defined. This class will itself be a template, and the template instantiation we choose will be determined by what sort of thing x
is.
This suggest that the very first thing we can write looks something like this:
template
struct stringifier
{
explicit stringifier(const T&) {}
std::ostream& output(std::ostream& s) const
{
return s << "";
}
};
template
inline std::ostream& operator<<(std::ostream& s,
const stringifier& t)
{
return t.output(s);
}
template
inline stringifier prettyprint(T&& t)
{
return stringifier(std::forward(t));
}
So far so good. Now we can print "<unknown>" for any type. We have a place to start specializing things.
A quick poke around C++ type support and some experiments soon fleshed out the plan:
- There are at least 6 types of callable things: functions, member functions,
std::function
s, bind expressions, lambdas, and objects that supportoperator()
(function objects). All of these are "unprintable". Lambdas are a kind of function object; I don't know whether they can be meaningfully distinguished or whether that is desirable. nullptr
is its own type and unprintable.enum class
values are not printable out-of-the-box; they will require a cast tostd::underlying_type
.pair
s should be easy to deal with, buttuple
s will require some effort. They are designed for static access, not iteration.
So now I have 4 broad categories I want to represent for printing: already-outputtable, unprintable, callable, and containers/pair
s/tuple
s. I decided to make tag types for each specializable thing so as to be able to select on them.
struct is_iterable_tag {};
struct is_pair_tag {};
struct is_tuple_tag {};
struct is_callable_tag {};
struct is_outputtable_tag {};
struct is_enum_tag {};
struct is_unprintable_tag {};
As luck would have it, several of these already have built-in support for detecting:
template
using is_unprintable = typename std::conditional<
std::is_union::value ||
std::is_class::value ||
std::is_null_pointer::value,
std::true_type, std::false_type>::type;
// similarly we can use:
// std::is_enum::value to detect enum or enum class
Detecting pair
s and tuple
s is also easy, using a standard specialization pattern:
template
struct is_pair : public std::false_type {};
template
struct is_pair> : public std::true_type {};
template
struct is_tuple : public std::false_type {};
template
struct is_tuple> : public std::true_type {};
A good setup so far. We can detect concrete types. Next, how to detect whether something already supports operator<<
.